0x00 无内鬼
很早以前审计过Spring Security的源码,个人感觉还是相对于shiro安全很多的,不过后面shiro也引入了一些字符的拦截机制,相比于从前,安全性已经提高很多了,最起码,出现的新洞都很水吧。
Spring Security最大的优势在于它实至名归的Security,它集成了很多东西,认证、鉴权、anti csrf、session manager、url字符拦截等等。
这篇文章会分享一个Spring Security存在的auth bypass问题,它不仅仅出现在Spring Security中,在曾经的Apache Shiro中,亦或者现在的Spring interceptor,都存在这个问题。而且,在开发编写认证鉴权配置时,很大的概率会出现。
附带着,也说了下在SpringBoot 1.x.x版本中,Spring Security、Apache Shiro、Spring Interceptor某些权限配置下的通用bypass。
0x01 小笔记
先来点小笔记,简单的把Spring Security关键点列出来一下,一个是Spring Security filter chains的默认(为什么叫默认,是因为可以自己定顺序,也可以自定义的关闭一些filter)执行顺序,另一个是url字符拦截点,最后一个是url匹配鉴权点。
默认filter和顺序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31-> DelegatingFilterProxy
-> FilterChainProxy(url字符拦截点)
-> WebAsyncManagerIntegrationFilter
-> SecurityContextPersistenceFilter
-> HeaderWriterFilter
-> CsrfFilter
-> LogoutFilter
-> AbstractAuthenticationProcessingFilter
-> UsernamePasswordAuthenticationFilter
-> DefaultLoginPageGeneratingFilter
-> DefaultLogoutPageGeneratingFilter
-> SecurityContextHolderAwareRequestFilter
-> AnonymousAuthenticationFilter
-> SessionManagementFilter
-> ExceptionTranslationFilter
-> FilterSecurityInterceptor(url匹配鉴权点)检查uri的代码点
1
2
3
4
5
6
7org.springframework.security.web.FilterChainProxy#doFilterInternal
-org.springframework.security.web.firewall.StrictHttpFirewall#getFirewalledRequest
-org.springframework.security.web.firewall.StrictHttpFirewall#rejectedBlacklistedUrls
-org.springframework.security.web.firewall.StrictHttpFirewall#isNormalized
rejectedBlacklistedUrls方法:
url不能包含以下子字符串(encodedUrlBlacklist),会检查request.getContextPath()和request.getRequestURI():1
// � %2F%2f %2F%2F %00 %25 %2f%2f %2f%2F %5c %5C %3b %3B %2e %2E %2f %2F ; \
url不能包含以下子字符串(decodedUrlBlacklist),会检查request.getServletPath()和request.getPathInfo():1
// � %2F%2f %2F%2F %00 % %2f%2f %2f%2F %5c %5C %3b %3B %2f %2F ; \
isNormalized:
不允许出现”.”, “/./“ or “/.”,检查request.getRequestURI() request.getContextPath() request.getServletPath() request.getPathInfo()
containsOnlyPrintableAsciiCharacters:
不允许出现 小于 ‘\u0020’ 或大于 ‘\u007e’ 的字符,request.getRequestURI()
上面的字符串拦截,还是挺苛刻的,但实际上,写一个特定的接口,加上认证鉴权,然后burp去遍历一下,大概率能fuzz出来。
- url匹配鉴权点
栈信息:1
2
3
4
5
6
7
8
9
10
11
12
13org.springframework.security.web.access.intercept.FilterSecurityInterceptor#invoke
org.springframework.security.access.intercept.AbstractSecurityInterceptor#beforeInvocation
org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource#getAttributes
org.springframework.security.web.util.matcher.AntPathRequestMatcher#matches
org.springframework.security.web.util.matcher.AntPathRequestMatcher.SpringAntMatcher#matches
org.springframework.util.AntPathMatcher#match
org.springframework.util.AntPathMatcher#doMatch
0x02 auth bypass
上图:
少了1
2curl 'http://127.0.0.1:8080/api/list' -> 403
curl 'http://127.0.0.1:8080/api/list/' -> 200
可以看到,在url最后加个/后缀,就能auth bypass了,主要原因是因为,加了/后缀,spring还能路由到controller接口,并且spring security的AntPathRequestMatcher在默认构造情况下是使用fullMatch = true的匹配模式
在匹配时,会先根据/斜杆,把url和pattern进行分组,比如1
2
3url: /user/threedr3am/info -> "user","threedr3am","info" - size=3
pattern: /user/*/info -> "user","*","info" - size=3
依次匹配,星号能匹配/斜杆以外的字符,所以,这个url和pattern的每一个元素能匹配上,且数组size也为3,那么,这个url和pattern就会返回true,但我们再加上/斜杆后缀的时候,情况变了,变成了1
2
3url: /user/threedr3am/info/ -> "user","threedr3am","info","" - size=4
pattern: /user/*/info -> "user","*","info" - size=3
那么,就无法匹配上了,故返回了false。无论是/api/,还是/admin,结果也是一样的,除非/api/改成/api/*,或者同时存在/api/和/api/*/。
所以,通过加/斜杆后缀,可以成功绕过匹配。Spring interceptor和老版本的Apache Shiro也是一样的,大差不差,利用这个特性也是能绕过匹配的。
PS: 并且在SpringBoot 1.x.x版本的某个加后缀的特性中,通过加.html等,也能绕过Spring Security、Apache Shiro最新版(现在应该是1.8.0)、Spring interceptor。
例:1
2
3url: /user/threedr3am/info.html
pattern: /user/*/info
0x03 开发&审计经验
一般情况下,开发者配置这些鉴权时,最好遵循一个原则,就是”优先使用/**认证兜底,明确哪些接口无需认证,而不是明确哪些接口需要认证”,什么意思呢?
就是,比如你写一个web系统,存在很多接口(/api/{name}/info、/api/user/list、/api/status),我优先使用/*去兜底,让所有接口需要认证,然后再明确哪些接口无需认证就可以访问,比如/api/status。而不是说,我明确/api//info需要认证,/api/user/*需要认证,这样的话,会很大几率存在一些绕过的问题。
讲得比较模糊,大概有以下两个例子说明:
没问题:1
2
3/api/status - 无需认证
/** - 需要认证
存在问题:1
2
3
4
5/api/*/info - 需要认证
/api/user/* - 需要认证
/api/status - 无需认证
也就是说,如果你遇到Spring Security、Apache Shiro、Spring Interceptor,你可以根据这个原则,去快速判断,这个web系统的鉴权认证配置,会不会有问题。有一个高star的开源项目alibaba-canal,它使用的Spring Interceptor配置,就是遵循这样的一个原则,可以参考以下。
0x04 test uri匹配(fullMatch = true)
对一些情况进行了简单的test后,得到的一个结果
1 |
|
如果认证鉴权配置使用了上面的pattern,则pass表示能匹配上,意味无法绕过,no表示不能匹配,可绕过
0x05 other
这是个很水的bypass,理论上应该能在url加入一些特殊字符,使其无法匹配上(默认情况下是fullMatch = true),进行绕过,这样才是最优的bypass,听说rr有,而我,不会,太菜了。